﻿
using System;
using System.Threading;

namespace CS.Scheduling
{
    /// <summary>
    /// 任务上下文内容，提供给调度器使用。
    /// <para>Contains the context of a managed job. Internally used by the scheduler.</para>
    /// </summary>
    public class JobContext
    {

        #region 公有属性

        /// <summary>
        /// 任务执行时的回调。
        /// <para>The callback action that is being invoked.</para>
        /// </summary>
        public Action<Job> CallbackAction { get; protected set; }

        /// <summary>
        /// 运行中的任务。
        /// <para>The maintained job.</para>
        /// </summary>
        public Job ManagedJob { get; set; }

        /// <summary>
        /// 任务最后一次运行的时间，如果没有执行过返回null。
        /// <para>The timestamp of the last job execution. Returns null if the job hasn't been executed yet. </para>
        /// </summary>
        public DateTimeOffset? LastJobEvaluation { get; set; }

        /// <summary>
        /// 任务下次运行的时间，任务完成后下次运行时间为null，即不再运行了。
        /// <para>The next execution time. Returns null if the job is done. After initialization, this is the job's <see cref="Job.StartTime"/>.</para>
        /// </summary>
        public DateTimeOffset? NextExecution { get; set; }

        /// <summary>
        /// 剩余执行次数，配置了需要运行次数的任务需要。
        /// <para>The remaining executions, if the job was configured to run a specific number of times.</para>
        /// </summary>
        public int? RemainingExecutions { get; set; }

        #endregion

        #region 任务内容构造

        /// <summary>
        /// 构造运行任务内容。
        /// <para>Initializes a new instance of the <see cref="T:System.Object"/> class.</para>
        /// </summary>
        /// <exception cref="ArgumentNullException">
        /// 参数null引用引发的异常。
        /// If one of the parameters is is a null reference.
        /// </exception>
        /// <exception cref="InvalidOperationException">
        /// 运行配置冲突异常。
        /// If the configuration does not allow proper scheduling, e.g. because several loops were specified, but the inverval is missing.
        /// </exception>
        public JobContext(Job managedJob, Action<Job> callbackAction)
        {
            if (managedJob == null) throw new ArgumentNullException("managedJob");
            if (callbackAction == null) throw new ArgumentNullException("callbackAction");

            //多次运行时需要时间间隔。
            //make sure we have an interval if we have more than one loop
            var interval = managedJob.Interval;
            if (interval == null)
            {
                //只运行一次时不引发异常。
                if (managedJob.Loops == null || managedJob.Loops.Value > 1)
                {
                    var msg = "Job [{0}] is invalid: Specifiy either a single run, or a loop interval.";
                    msg = String.Format(msg, managedJob.JobId);
                    throw new InvalidOperationException(msg);
                }
            }

            ManagedJob = managedJob;
            CallbackAction = callbackAction;
            NextExecution = managedJob.StartTime;
            RemainingExecutions = managedJob.Loops;

            //启动时间调整，下次运行时间小于当前时间时重设下次运行时间为当前时间。
            //adjust starting time if the initial time was in the past,but rather start immediately
            var now = SystemTime.Now();
            if (NextExecution.Value < now) NextExecution = now;
        }

        #endregion

        #region 相关可重写的虚方法

        /// <summary>
        /// 通过线程池执行运行中的任务，并更新任务的状态。
        /// <para>Invokes the managed job's <see cref="CallbackAction"/> through the thread pool, and updates the job's internal state.</para>
        /// </summary>
        public virtual void ExecuteAsync(Scheduler scheduler)
        {
            //只执行符合执行条件的任务。
            //only execute if the job is neither aborted, expired, or paused
            if (ManagedJob.State == JobState.Active && (ManagedJob.ExpirationTime == null || ManagedJob.ExpirationTime >= SystemTime.Now()))
            {
                //Note:QueueUserWorkItem的用法
                ThreadPool.QueueUserWorkItem(s =>
                {
                    try
                    {
                        CallbackAction(ManagedJob);
                    }
                    catch (Exception e)
                    {
                        //Todo: 理解中...
                        //do not reference the scheduler instance to avoid closure
                        var sch = (Scheduler)s;
                        if (!sch.SubmitJobException(ManagedJob, e)) throw;
                    }
                }, scheduler);
            }
            UpdateState();
        }


        /// <summary>
        /// 执行后更新状态，并更新任务最后运行的时间和下一次执行任务的时间。
        /// <para>Updates the internal state after an execution, and updates the <see cref="LastJobEvaluation"/> and <see cref="NextExecution"/> values. If the job is not supposed to run anymore, the <see cref="NextExecution"/> property is set to null.</para>
        /// </summary>
        protected virtual void UpdateState()
        {
            LastJobEvaluation = SystemTime.Now();
            lock (ManagedJob.SyncRoot)
            {
                if (ManagedJob.State == JobState.Canceled)
                {
                    NextExecution = null;
                    return;
                }

                if (RemainingExecutions.HasValue)
                {
                    //更新剩余运行次数
                    //only decrease the loop counter if the job is not paused
                    if (ManagedJob.State == JobState.Active)
                    {
                        RemainingExecutions--;
                    }

                    //最后一次时运行完成
                    //we're done if we peformend the last loop
                    if (RemainingExecutions == 0)
                    {
                        //we have a loop, and completed it
                        ManagedJob.State = JobState.Finished;
                        NextExecution = null;
                        return;
                    }
                }

                //非间隔运行时取消
                //if there is no reoccurrence interval, we cannot calculate a new run -> cancel
                if (!ManagedJob.Interval.HasValue)
                {
                    ManagedJob.State = JobState.Canceled;
                    NextExecution = null;
                    return;
                }

                //更新下次运行时间
                //schedule next execution - even if this is beyond expiration
                //(we don't cancel as long as expiration happened)
                NextExecution = (NextExecution.Value).Add(ManagedJob.Interval.Value);

                //更新有过期设定的任务状态
                //if the next job is beyond expiration, reset again
                if (ManagedJob.ExpirationTime.HasValue && LastJobEvaluation.Value > ManagedJob.ExpirationTime)
                {
                    ManagedJob.State = JobState.Finished;
                    NextExecution = null;
                }

            }
        }

        #endregion

    }
}